#ifndef MOVEOPERA_H
#define MOVEOPERA_H

#include <BMap>
#include "Opera.h"

class TransOpera : public Opera
{
public:
	void setMatrix(const BMatrix& matrix)
	{
		_matrix = matrix;
	}
	const BMatrix& matrix() const
	{
		return _matrix;
	}

	void setOrigin(const BMatrix& origin)
	{
		_origin = origin;
	}
	const BMatrix& origin() const
	{
		return _origin;
	}

protected:
	BMatrix		_matrix;
	BMatrix		_origin;
};

class NodeTransOpera : public TransOpera
{
public:
	NodeTransOpera(BNode* node)
	{
		_node = node;
		_origin = node->matrix();
	}

	BNode* node()
	{
		return _node;
	}

	virtual bool redo()
	{
		if (!_node)
			return false;
		_node->setMatrix(_matrix);
		return true;
	}
	virtual bool undo()
	{
		if (!_node)
			return false;
		_node->setMatrix(_origin);
		return true;
	}

private:
	BNode*		_node;
};

class ShapeTransOpera : public TransOpera
{
public:
	ShapeTransOpera(BShape* shape, BNode* parent = 0)
	{
		_shape = shape;
		_parent = parent;
		_origin = _shape->matrix();
	}

	BShape* shape()
	{
		return _shape;
	}
	BNode* parent()
	{
		return _parent;
	}

	virtual bool redo()
	{
		if (!_shape)
			return false;
		_shape->setMatrix(_matrix);
		return true;
	}
	virtual bool undo()
	{
		if (!_shape)
			return false;
		_shape->setMatrix(_origin);
		return true;
	}

private:
	BShape*		_shape;
	BNode*		_parent;
};

class GroupTransOpera : public TransOpera
{
public:
	GroupTransOpera(BGroup* group)
	{
		_groups.append(group);
		BSpace space;
		collectGroup(group, space);
		_origin = space.center();
	}
	GroupTransOpera(const BGroupHolderArray& groupSet, BNode* parent = 0)
	{
		BSpace space;
		for (int i = 0; i < groupSet.size(); i++)
		{
			BGroup* group = groupSet[i];
			collectGroup(group, space);
		}
		_origin = space.center();
	}

	void collectGroup(BGroup* group, BSpace& space)
	{
		space.expand(group->space());
		for (int i = 0; i < group->shapeCount(); i++)
		{
			BShape* shape = group->shape(i);
			_shapeOrigins[shape] = shape->matrix();
			_shapeMatrixs[shape] = shape->matrix();
		}
		for (int i = 0; i < group->nodeCount(); i++)
		{
			BNode* node = group->node(i);
			_nodeOrigins[node] = node->matrix();
			_nodeMatrixs[node] = node->matrix();
		}
		for (int i = 0; i < group->childCount(); i++)
		{
			collectGroup(group->child(i), space);
		}
	}

	const BGroupArray& groupSet()
	{
		return _groups;
	}

	virtual bool redo()
	{
		BMatrix invOrigin = _origin.inverse();
		for (auto it = _shapeOrigins.begin(); it != _shapeOrigins.end(); it++)
		{
			BShape* shape = it.key();
			BMatrix matrix = it.value() * invOrigin;
			matrix *= _matrix;
			shape->setMatrix(matrix);
		}
		return true;
	}
	virtual bool undo()
	{
		for (auto it = _shapeOrigins.begin(); it != _shapeOrigins.end(); it++)
		{
			BShape* shape = it.key();
			BMatrix& matrix = it.value();
			shape->setMatrix(matrix);
		}
		return true;
	}

private:
	BGroupArray		_groups;
	BMap<BShape*, BMatrix>	_shapeOrigins;
	BMap<BShape*, BMatrix>	_shapeMatrixs;
	BMap<BNode*, BMatrix>	_nodeOrigins;
	BMap<BNode*, BMatrix>	_nodeMatrixs;
};

class MultiTransOpera : public TransOpera
{
public:
	MultiTransOpera()
	{

	}

	void insertShape(BShape* shape, BNode* node = 0)
	{
		_shapeOrigins[shape] = shape->matrix();
		_shapeMatrixs[shape] = shape->matrix();
	}
	void addNode(BNode* node)
	{
		_nodeOrigins[node] = node->matrix();
		_nodeMatrixs[node] = node->matrix();
	}
	void addGroup(BGroup*& group)
	{
		BSpace space;
		collectGroup(group, space);
		_origin = space.center();
	}

	void collectGroup(BGroup* group, BSpace& space)
	{
		space.expand(group->space());
		for (int i = 0; i < group->shapeCount(); i++)
		{
			BShape* shape = group->shape(i);
			_shapeOrigins[shape] = shape->matrix();
			_shapeMatrixs[shape] = shape->matrix();
		}
		for (int i = 0; i < group->nodeCount(); i++)
		{
			BNode* node = group->node(i);
			_nodeOrigins[node] = node->matrix();
			_nodeMatrixs[node] = node->matrix();
		}
		for (int i = 0; i < group->childCount(); i++)
		{
			collectGroup(group->child(i), space);
		}
	}

	const BGroupArray& groupSet()
	{
		return _groups;
	}

	virtual bool redo()
	{
		for (auto it = _shapeMatrixs.begin(); it != _shapeMatrixs.end(); it++)
		{
			BShape* shape = it.key();
			BMatrix& matrix = it.value();
			shape->setMatrix(matrix);
		}
		for (auto it = _nodeMatrixs.begin(); it != _nodeMatrixs.end(); it++)
		{
			BNode* node = it.key();
			BMatrix& matrix = it.value();
			node->setMatrix(matrix);
		}
		return true;
	}
	virtual bool undo()
	{
		for (auto it = _shapeOrigins.begin(); it != _shapeOrigins.end(); it++)
		{
			BShape* shape = it.key();
			BMatrix& matrix = it.value();
			shape->setMatrix(matrix);
		}
		for (auto it = _nodeOrigins.begin(); it != _nodeOrigins.end(); it++)
		{
			BNode* node = it.key();
			BMatrix& matrix = it.value();
			node->setMatrix(matrix);
		}
		return true;
	}

private:
	BGroupArray		_groups;
	BMap<BShape*, BMatrix>	_shapeOrigins;
	BMap<BShape*, BMatrix>	_shapeMatrixs;
	BMap<BNode*, BMatrix>	_nodeOrigins;
	BMap<BNode*, BMatrix>	_nodeMatrixs;
};

class MultiNodeTransOpera : public TransOpera
{
public:
	MultiNodeTransOpera(const BNodeHolderArray& nodes)
	{
		_nodes = nodes;
		for (int i = 0; i < nodes.size(); i++)
		{
			BNode* node = nodes[i];
			_matrixMap[node] = node->matrix();
			_originMap[node] = node->matrix();
		}
	}

	const BNodeArray& nodes()
	{
		return _nodes;
	}

	virtual bool redo()
	{
		for (int i = 0; i < _nodes.size(); i++)
		{
			BNode* node = _nodes[i];
			const BMatrix& matrix = _matrixMap[node];
			node->setMatrix(matrix);
		}
		return true;
	}
	virtual bool undo()
	{
		for (int i = 0; i < _nodes.size(); i++)
		{
			BNode* node = _nodes[i];
			const BMatrix& matrix = _originMap[node];
			node->setMatrix(matrix);
		}
		return true;
	}

private:
	BNodeArray				_nodes;
	BMap<BNode*, BMatrix>	_matrixMap;
	BMap<BNode*, BMatrix>	_originMap;
};

#endif